package com.example;

import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfSignatureAppearance;
import com.itextpdf.text.pdf.PdfStamper;
import com.itextpdf.text.pdf.security.BouncyCastleDigest;
import com.itextpdf.text.pdf.security.DigestAlgorithms;
import com.itextpdf.text.pdf.security.ExternalDigest;
import com.itextpdf.text.pdf.security.ExternalSignature;
import com.itextpdf.text.pdf.security.MakeSignature;
import com.itextpdf.text.pdf.security.PrivateKeySignature;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.cert.Certificate;

/**
 * @Date 2022/4/6
 * @Author  Jonathan
 * @link {https://github.com/ETDA/PDFSigningAndTimestamp}
 */
public class SignUtil {

    private static final org.slf4j.Logger log = org.slf4j.LoggerFactory.getLogger(SignUtil.class);
    public static final char[] PASSWORD = "123456".toCharArray();// keystory密码

    /**
     * 单多次签章通用
     *
     * @param src    待签署文件
     * @param target 签署完成的文件
     */
    @SuppressWarnings("resource")
    public void sign(String src, String target, SignatureInfo signatureInfo) {
        InputStream inputStream = null;
        FileOutputStream outputStream = null;
        ByteArrayOutputStream result = new ByteArrayOutputStream();
        try {
            // 读取原文件
            inputStream = new FileInputStream(src);
            ByteArrayOutputStream tempArrayOutputStream = new ByteArrayOutputStream();
            PdfReader reader = new PdfReader(inputStream);

            // 创建签章工具PdfStamper ，最后一个boolean参数是否允许被追加签名
            // false的话，pdf文件只允许被签名一次，多次签名，最后一次有效
            // true的话，pdf可以被追加签名，验签工具可以识别出每次签名之后文档是否被修改
            PdfStamper stamper = PdfStamper.createSignature(reader, tempArrayOutputStream, '\0', null, true);

            // 获取数字签章属性对象
            PdfSignatureAppearance appearance = stamper.getSignatureAppearance();
            appearance.setReason(signatureInfo.getReason());
            appearance.setLocation(signatureInfo.getLocation());

            // 设置签名的位置，页码，签名域名称，多次追加签名的时候，签名预名称不能一样 图片大小受表单域大小影响（过小导致压缩）
            // 签名的位置，是图章相对于pdf页面的位置坐标，原点为pdf页面左下角
            // 四个参数的分别是，图章左下角x，图章左下角y，图章右上角x，图章右上角y
            Rectangle rectangle = new Rectangle(signatureInfo.getRectllx(), signatureInfo.getRectlly(), signatureInfo.getRecturx(), signatureInfo.getRectury());
            appearance.setVisibleSignature(rectangle, 1, signatureInfo.getFieldName());

            // 读取图章图片
            Image image = Image.getInstance(signatureInfo.getImagePath());
            appearance.setSignatureGraphic(image);
            appearance.setCertificationLevel(signatureInfo.getCertificationLevel());

            // 设置图章的显示方式，如下选择的是只显示图章（还有其他的模式，可以图章和签名描述一同显示）
            appearance.setRenderingMode(signatureInfo.getRenderingMode());

            // 摘要算法
            ExternalDigest digest = new BouncyCastleDigest();

            // 签名算法
            ExternalSignature signature = new PrivateKeySignature(signatureInfo.getPk(), signatureInfo.getDigestAlgorithm(), null);

            // 调用itext签名方法完成pdf签章 //数字签名格式，CMS,CADE
            MakeSignature.signDetached(appearance, digest, signature, signatureInfo.getChain(), null, null, null, 0, MakeSignature.CryptoStandard.CADES);
            inputStream = new ByteArrayInputStream(tempArrayOutputStream.toByteArray());
            // 定义输入流为生成的输出流内容，以完成多次签章的过程
            result = tempArrayOutputStream;

            outputStream = new FileOutputStream(target);
            outputStream.write(result.toByteArray());
            outputStream.flush();

        } catch (Exception e) {
            log.error("error", e);
        } finally {
            try {
                if (null != outputStream) {
                    outputStream.close();
                }
                if (null != inputStream) {
                    inputStream.close();
                }
                result.close();
            } catch (IOException e) {
                log.error("Error", e);
            }
        }
    }

    public static void main(String[] args) {
        try {
            SignUtil app = new SignUtil();
            // 将证书文件放入指定路径，并读取keystore ，获得私钥和证书链
            String pkPath = "D:/keys/client1.p12";
            KeyStore keyStore = KeyStore.getInstance("PKCS12");
            keyStore.load(new FileInputStream(pkPath), PASSWORD);
            String alias = keyStore.aliases().nextElement();
            PrivateKey pk = (PrivateKey) keyStore.getKey(alias, PASSWORD);
            log.info("privateKey:{}", pk);
            // 得到证书链
            Certificate[] chain = keyStore.getCertificateChain(alias);
            // 封装签章信息
            SignatureInfo signInfo = new SignatureInfo();
            signInfo.setReason("这是一个牛逼的签名");
            signInfo.setLocation("这就是位置呢");
            signInfo.setPk(pk);
            signInfo.setChain(chain);
            signInfo.setCertificationLevel(PdfSignatureAppearance.NOT_CERTIFIED);
            signInfo.setDigestAlgorithm(DigestAlgorithms.SHA256);
            signInfo.setFieldName("demo");

            // 签章图片
            signInfo.setImagePath("D:/keys/sign.jpg");
            signInfo.setRenderingMode(PdfSignatureAppearance.RenderingMode.GRAPHIC);

            // 印章位置
            signInfo.setRectllx(100);  // 值越大，代表向x轴坐标平移 缩小 （反之，值越小，印章会放大）
            signInfo.setRectlly(200);  // 值越大，代表向y轴坐标向上平移（大小不变）
            signInfo.setRecturx(400);  // 值越大   代表向x轴坐标向右平移  （大小不变）
            signInfo.setRectury(100);  // 值越大，代表向y轴坐标向上平移（大小不变）

            // 原PDF和签名后的PDF
            app.sign("D:/keys/这是一个合同呢.pdf", "D:/keys/demo2_output.pdf", signInfo);
        } catch (Exception e) {
            log.error("error", e);
        }
    }
}
